Javascript 问题汇总:

https://github.com/simongong/js-stackoverflow-highest-votes


Variables

Five Simple:

  • Undefined: nothing
  • Null: a variable with null value
  • Boolean: true/false
  • Number: number & NAN (any calculation within NAN is NAN, NAN != NAN)
  • String: text

注意: 其中,undefined, null, ‘’, 0 都等价于 false,但是只有 undefined == null, 其余不互等。再次,123 == '123' 成立,但是 123 === ‘123’ 判断类型,不成立 . moo

全局变量 & 命名空间

不加var 以及 在所有函数外部定义的变量 是全局变量, 全局变量其实是在一个叫做 window 的命名空间里,如下代码

var foo = "foo";
console.log(foo); 	// "foo"
console.log(window.foo)		// "foo"

然而不同的Javascript 文件如果使用了同名的全局变量,或者同名的顶层函数,都会造成命名冲突,并且很难被发现,所以减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中,如下

// 唯一的全局变量MYAPP:
var MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {
    return 'foo';
};

把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。许多著名的JavaScript库都是这么干的:jQuery (&),underscore (_)等等。


Object

创建Object

  • Curly Braces
  • new Object()
  • Object.create
// 直接创建  Object Literals
var cat = {
  color: white,
  age: 24
};

// Constructor Functions
var cat = new Object();

// 原型创建  Object.Create
var cat = Object.create(Object.prototype, {
    color: {
      value: 'white',
      enumerable: true,
      writeable: true,
      configurable: true
    },
      age: {
      value: 24,
      enumerable: true,
      writeable: true,
      configurable: true
    }
})

使用Object

  • dot notation
  • bracket notation
var cat = new Cat('kitty', 3);

// dot notation
cat.name;	// 'kitty'

// bracket notation
cat['age']	// 3

bracket notation is useful when you have invalid attributes name like “eye color”

cat['eye color'] = 'green';

删除属性 (delete)

delete cat.name;		// delete key from the hashmap, but very slow
//or
delete cat['name'];
//or
cat.name = undefined; 		// alternative, but highly recommended in terms of performance

注意,delete 方法要比定义undefined 方法慢100%, 所以推荐使用undifined 方法。

但是undefined 方法还是会在hashmap 里留下键值对,所以在例如for in loop 中还是会被遍历。

遍历Object (for … in …)

for (var propertyName in cat) {
    console.log(propertyName);
    console.log(cat[propertyName]);
}

此外,还有 Object.keys(cat) 的方法列出所有的keys

Description

每一个object 属性都有一个对应的description preporty 去描述,包含 value, writable, enumerable, 以及 configurable 四个属性。属性可以用 Object.defineProperty 方法去更改

var cat = {
  name: 'tom',
  age: 2
};

Object.getOwnPropertyDescriptor(cat, 'name');
// Object {
//   value: name
//   writable: true
//   enumerable: true
//   configurable: true
// }
  • writable

    定义这个变量是否可以被改变,如果修改一个不可变的变量,会报错。

    其实es6 种的let 和 const 的区别就是定义变量时这个属性不同

    Object.definedProperty(cat, 'name', {writable: false});
    cat.name = 'something else';
    // cannot assign to read only property 'name' of <Object>
    
  • enumerable

    定义这个变量是否可以被遍历,如果遍历一个含不可遍历变量的对象,该变量不会显示

    连计算长度的方法.length 都不会计算这个变量,但是可以访问,可以修改

  • configurable

    定义这个变量的属性(上述三个)是否可以被改变。

    例如,如果变量 Object.defineProperty(cat, 'name', {configurable: false})

    那么如果我们再次修改它的writable 或者 enumerable, 甚至删除都会报错 Cannot redefine property

Getter&Setter

Getter 和 Setter 是OOP 语言中的一种特性,用于封装借口的中间件,例如类内部private 变量在外部无法访问,所以通过一对Getter 和 Setter 方法去访问这个内部值。

但是在Javascript 中,Getter 和 Setter 属性 却可以被用来监听Object 中变量的变化

var cat = {
  name: {first: 'Meow', last: 'Zhang'},
  age: 2
};

首先,getter 和 setter 成对出现,用来定义一个方法去访问Object 内部的值,因此在getter 和 setter 内部是可以使用this 去指代object 的,例如下

Object.defineProperty(cat, 'fullName', {
  get: function () {
    return this.name.first + " " + this.name.last;
  },
  set: function (val) {
    var newName = val.split(' ');
    this.name.first = newName[0];
    this.name.last = newName[1];
  }
})

cat.fullName; 		// Meow Zhang
cat.fullName('Ethan Yu');		//first: Ethan, last: Yu

我们给cat 添加了一个fullName 属性,直接调用会触发get 方法,直接改变会触发set 方法。下面例子说明如何监听值的变化。

我们当然可以直接用cat.name.first = 'Meooooooow' 去改变名称,但是我们在getter setter 中可以调用this,因而可以在改变值的过程中监听并进行其他操作。

Object.defineProperty(cat, 'fullName', {
    get: function () {
      console.log('Listening someone try to get value, do something')
      return this._fullName;
    },
  	set: function (val) {
      console.log('Listening someone try to change value, change FirstName and LastName')
      var newName = val.split(' ');
      this.name.first = newName[0];
      this.name.last = newName[1];
      this._fullName = val;
    }
});

cat.fullName;	// warning , undefined.
cat.fullName = 'Ethan Zhang';	// warning, change value
cat.fullName;	// warning, Ethan Zhang

上述例子给通过Getter / Setter 给Cat 新增了一个变量,并且在访问/修改这个值的时候,可以被监听从而做其他的事情。Vue 的双向绑定就是这个原理。


Function

函数类型与预编译

定义式函数:function muFunc() {...}

变量式函数:var myFunc = function(){...}

二者内部定义相同,区别在于JS不是一句一句执行代码,而是一段一段<script>进行分析。 在一段代码中,定义式函数 ( function myFUn(){...} ) 会优先执行,而变量式会按顺序执行

typeof(myfunc) === ‘function’

// 举例来说,如果在同一段代码中重复定义重名函数,定义式的函数语句会被提取出来优先执行
function myfunc ()
{
  alert("hello");
}; 
myfunc(); //输出yeah,因为已经定义过

function myfunc ()
{
  alert("yeah");
};    
myfunc(); //当然输出yeah

有人说javascript 优先执行定义式函数,所以把这一步称之为**“Javascript预编译”**

实际上,js 预编译除了有 优先执行定义式函数 外,还会 优先执行var变量的创建(此时创建的是值为undefined的var,要等到一行一行执行代码时,执行到赋值语句才会赋值,如var test=1)

  • 没有return的函数也会返回,返回值为 undefined

函数作用域

JS 中的全局就是一个对象,在浏览器中就是window,在node 中就是 //TODO:

JS 不是 block-level 的作用域级别划分,而是通过 function-level 来划分作用域

这里区分一下 var 和 let

var 的作用域是最近的函数作用域 let 的作用域是最近的 闭合 函数作用域,比var 小 不用任何前缀定义的是全局变量

Function & Object (函数对象化)

function 其实就是一个 object,因为它具有 object 的一切能力,唯一区别在于 function 比一般 object 多了一个括号操作符 (), 这个括号用来执行函数的逻辑,即,函数本身还可以被调用,一般对象却不可以被调用,除此之外完全相同。

    function Sing()
    {
        with(arguments.callee)
          alert(author + ":" + poem);               // 方法
    };
    Sing.author = "李白";     // 属性
    Sing.poem = "汉家秦地月,流影照明妃。一上玉关道,天涯去不归"; 
    Sing();             // 调用
    Sing.author = "李战";
    Sing.poem = "日出汉家天,月落阴山前。女儿琵琶怨,已唱三千年";
    Sing();

This

在其他面向对象语言如C#, Java 中,this 一般指对象自己,但是 js 中 this 指向调用他的函数的作用域

Apply, Call & Bind: set the this value (context) for a function, for example

var Bob = {
  name: 'Bob', 
  intro: function () {return 'Bob\'s name is ' + this.name}
}

var Tom = {
  name: 'Tom', 
  intro: function () {return 'Tom\'s name is ' + this.name}
}

Bob.intro(); 		// Bob's name is Bob
Tom.intro();		// Tom's name is Tom

// Bob's function use `this` of tom
Bob.intro.apply(Tom);	// Bob's name is Tom
// same to Bob.intro.call(Tom);
// same to Bob.intro.bind(Tom)();

We only use these methods when we want to borrow a method from other Object.

DIFFERENCE:

  • APPLY: Object.function.apply(targetObj, [arg1, arg2, ...])
  • CALL: Object.function.call(targetObj, arg1, arg2, ...)
  • BIND: Object.function.bind(targetObj, arg1, arg2, ...)()

Javascript 面向对象

Javascript 是一款基于原型模式的面向对象语言。

//构造函数
function Worker(name, age){
  this.name = name;
  this.age = age;
  this.isworking = false;
  this.starkWorking = function(){
    this.isworking = true;
  }
}
var tom = new Worker("Tom", 24);	//新建实例
tom.startWorking();		//调用实例上的方法

New

如果不用new 关键字,那么上述的worker 就只是一个普通的function,其中的this关键字指向的就是直接调用这个方程的对象,如下例

// In this case, window object call the Worker
var tom = Worker("Tom", 24);
console.log(tom.name);	// undefined
console.log(window.name); // Tom

在如上例子中,因为直接调用worker function 的对象是全局变量window,所以变量tom 实际上没有被返回任何值,反而是全局变量windowthis 关键词赋予了诸多属性。

New 关键词实际上进行了三个步骤:

  1. 创建一个新的空的对象

    var tom = {};
    
  2. 将类的prototype 属性复制给新对象的 _proto_ 属性,这样新对象就拥有同样的作用域

    tom._proto_ = worker.prototype;
    
  3. 但此时新对象还不能访问类的内部变量,所以要通过call 方法让新对象可以调用内部变量

    换言之,worker 的 this 指向了 tom

    worker.call(tom, "Tom", 24);
    

至此,一个新的实例 (object or json) 创建完毕。

可是,这些成员并不能共用,如

var tom = new worker("Tom", 24);
var lee = new worker("Lee", 27);
tom.isworking == lee.isworking 	// ==> false

可见,isworking 的初始属性并不共享,对每一个实例都会有三个属性和一个方法。

所以,当实例有很多的时候,内存开销非常大,就需要使用原型

Prototype

原型设计中,有三个概念必须了解并区分清楚:

constructor, prototype, __proto__

Constructor

Constructor 属性永远指向创建当前对象的构造函数 !important

[1, 2, 3].constructor					// function Array()
{a: 'test'}.constructor					// function Object()
false.constructor						// function Boolean()
"Hello".constructor						// function String()
function(){}.constructor				// function Function()

// 通过构造函数创建instance
function A(){};
var a = new A();
a.constructor;			// function A(){};

上述代码说明了任何一个对象都拥有constructor 属性,指向创建这个对象的构造函数

简言之,谁创建,谁构造

Prototype

只有函数对象才有 prototype 属性,称为原型对象。

prototype (原型对象) 是一个object, 包括原型上的属性和方法,还有一个constuctor

// Worker.prototype
{
    nation: 'CHN',
    isWorking: function(){},
    ...,
    constructor: Worker(){}
}

以原型对象为构造函数创建出来的instance 都可以访问原型对象的属性和方法

// 所以上面Worker 例子中
Worker.prototype.isResting = function(){};
Tom = new Worker();
Lee = new Worker();
Tom.isWorking == Lee.isWorking // => false
Tom.isResting == Lee.isResting // => true

Constuctor & Prototype

每个函数都有construtor 和 prototype, 它prototype的constructor 指向自己

//最标准的函数 或者 类的形式
function Worker(){};
Worker.prototype.nation = 'CHN';
tom = new Worker();

tom.constructor // => Worker(){}
Worker.constructor // => function Function()
Worker.prototype // => {nation:'CHN', constructor: Worker}
Worker.prototype.constructor // => Worker(){} 

上面是最标准的模样,之后在建类等等的时候可能会手动修改他们的 constructor 和 prototype,在修改完后应该(非必需)把他们的constructor 等等补齐

_proto_

Javascript 里所有的变量本质上都是object,每个object都有__proto__ 属性

Javascript 使用了链式继承,所以任何一个变量都有一个 __proto__ 属性,并且var.__proto__ === var.constructor.prototype ,被创建出来的instance 就可以继承类 (var.constructor) 在原型 (prototype) 上的属性和方法。

function Person (name) {
    this.name = name
}
Person.prototype.nation = 'CHN';
var ethan = new Person("Ethan");

ethan.__proto__	// => {nation: 'CHN', constructor: Person(){}}
ethan.constructor // => Person(){}
ethan.constructor.prototyoe // => {nation:.., constructor: Person}

ethan.__proto__ === ethan.constructor.prototype // => true

__proto__ 和 prototype 共同作用形成了继承链

那么在设计JavaScript面向对象类型的时候,我们一般遵循以下规则:

  1. 因为实例不同而不同的内容,用this关键字声明
  2. 无论实例怎样内容完全相同的成员,定义在prototype上
  3. 定义原型一般在函数外部,以 class.prototype.method = … 的形式定义

这意味着,我们可以给任何类添加自己想要的原型方法,如为DOM元素添加方法

当创建一个p元素时,它在原型链上继承了element对象的原型,也会有hide方法

简单的记录一个创建p标签的原型链,便于理解和拓展:

null > Object.prototype > Node.prototype > Element.prototype > HTMLElement.prototype > HTMLParagraphElement.prototype > document.creatElement('p')

这些其实就是一个一个的类和继承类,子类包含所有父类的属性和方法,如果修改在了 HTMLAnchorElement.prototype 上(a标签),和p没有继承关系,那么p标签就不会识别hide方法。

再例如创建注释 comment 的原型链:

null > Object.prototype > Node.prototype > CharacterData.prototype > Comment.prototype > document.creatComment('this is a comment')

注意:不建议在生产环境中拓展DOM原型,因为不同浏览器开发DOM原型权限的程度不同。此外,DOM原型非常不稳定,根据DOM Level 2定义,你是正工作在一个允许表现的难以捉摸和完全不稳定的东西上。

继承

在充分了解了 constructor prototype 和 __proto__ 的基础上,开始看继承

Javascript 的继承有以下几种方式:

  • Javascript 构造函数和继承成员

    function People(name, age) {
      this.name = name;
      this.age = age;
    };
    People.prototype.speak = function () {console.log(".")};
    
    // 1. 复制所有构造函数里的属性和和方法
    // 但是还没有继承到prototype 上的属性和方法
    function Man(name, age, sex){		
      People.apply(this, arguments);	// 复制属性和方法
      this.sex = sex;
    }
    
    // 测试
    var ethan = new Man('Ethan', 25, 'Male');
    ethan.name;		// 'Ethan'
    ethan.speak();	// ethan.speak is not a function
    
    // 2. 继承原型链上的属性和方法到 __proto__
    Man.prototype = Object.create(People.prototype);	// or
    Man.prototype = new People();	// or
    Object.setPrototypeOf(Man.prototype, People.prototype); // 推荐
    
    // 测试
    var tom = new Man('Tom', 25, 'Male');
    tom.name;		// 'Tom'
    tom.speak();	// .
    
    // 此时 Man.prototype 是 
    // { 
    //	  constructor: People, 
    //	  _proto_{speak: function(){}}
    // }
    // 3. 但是 function 的 prototype.constructor 是自己,所以重新赋值
    Man.prototype.constructor = Man;
    
    // 如果使用 Object.setPrototypeOf 方法就不用第三步
    

    apply / call: 把 Peoplethis 定义到 man 里,相当于如下

    // People.apply(this, arguments) 等价于
    function Man (name, age) {
      this.name = name;
      this.age =age;
    }
    

    无论 apply 还是 call 都只为复制构造函数,与类无关

  • ES6 Class 语法糖


Design Pattern

alt

Creational Pattern

Contructor Pattern

Constructing new objects with their own object scope

  • how the new keyworks works
  • how Angular / Vue construct object / data
Module Pattern

A easy way to encapsulate methods, like choosing function to use from a toolbox

  • common.js (Node) use a module.export = test & require('./test')

    // task.js
    var task = function () {
      //    return {
      //        var get = function () {...}
      //    }
      var get = function () {...};
      var save = function () {...};
                              
      return {
          get: get,
          save: save
      }
    }
    
    module.export = task;
    
    // main.js
    var task = require('./task.js');
    var test = task.get();
    
  • ES6 Module

Factory Pattern

Simplifies object creation and create them based on need

情景假设,如果我们有10个 vars 需要去引用

var fun1 = require('./fun1.js');
...
var func10 = require('./fun10.js');

var test = fun1.mtd();
var test2 = fun10.mtd();

这样引用很多不好也不方便,所以建立一个repoFactory.js

var repoFactory = function () {
  this.getRepo = function (repoType) {
    if (repotype = 'fun1') {
      return require('./fun1.js')
    }
    ...
    if ()
  }
}

Architectural Pattern

MVC

WHY

The Model-View-Controller pattern tried to separate UI (View) from Data-source (Model) directly while a controller would take care of user interaction.

alt

(虚线是通知,实线是调用)

In this way, everytime user interactive with the app, controller deal with the logic, then ask model.updateData() and then view.render().

DEFECT

Everytime you write something new, you need to take care of model update and view render through controller, which makes controller massive once logic get complex.

Also, controller can access both model and view, view can access both model and controller, model can access view

Highly coupled

MVP

Model-View-Presenter is the improved version of MVC, it only change controller to presenter to take care of the business logic.

Compare to MVC, the view in MVP only connect with presenter/controller rather than connect model and controller. So it decoupled view and model.

presenter can access both model and view, view can access both presenter.

Less coupled

MVVM

The Model-View-ModelView pattern tried to remove the interface between view and model, data can directly communicate with view through data-binding (Observer Pattern or Publish-Subscribe Pattern).

MVVM automic the process of binding data to view, so we don’t need to manually view.render() to update view in controller/presenter.


ES6

let & const

  • const 用来声明变量,但是声明的是常量。一旦声明,常量的值就不能改变。 需要注意的是,对常量重新赋值不会报错,只会默默地失败。

    由于 const 命令只是指向变量所在的地址,所以将一个对象声明为常量必须非常小心。

    const foo = {};
    foo.prop = 123;
    
    foo.prop
    // 123
    
    foo = {} // 不起作用
    

    上面代码中,常量 foo 储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把 foo 指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

  • let 实际上为 JavaScript 新增了块级作用域。

    function f1() {
      let n = 5;
      if (true) {
          let n = 10;
      }
      console.log(n); // 5
    }
    

    上面的函数有两个代码块,都声明了变量 n,运行后输出 5。这表示外层代码块不受内层代码块的影响。 如果使用 var 定义变量 n,最后输出的值就是 10。

  • 全局对象是最顶层的对象,在浏览器环境指的是 window 对象,在 Node.js 指的是 global 对象。在 JavaScript 语言中,所有全局变量都是全局对象的属性。 ES6 规定,var 命令和 function 命令声明的全局变量,属于全局对象的属性;let 命令、const 命令、class 命令声明的全局变量,不属于全局对象的属性。

    var a = 1;
    // 如果在node环境,可以写成global.a
    // 或者采用通用方法,写成this.a
    window.a // 1
    
    let b = 1;
    window.b // undefined
    

Module

在ES6 之前,JS 模块化主要由社区的 CommonJS (server-side) 和 AMD (browser-side) 两种方案组成。ES6 的模块功能可以完全取代二者,成为前后端通用的 JS 模块化解决方案。

ES6 采用尽可能的静态化编译,使得编译时就可以确定模块的依赖关系。而上两者是运行时编译,例如 require('fs') 导入的是一个编译好的对象,只有在运行时才能确定依赖关系。

而 es6 所谓静态化编译,就是说导出导入的并不是 Object,而是特定的代码,由 export 命令显式指定输出的代码,再通过 import 命令输入。

Export / Import

一个模块内部的变量都是局部的,外部无法访问,如果需要让外部获取这个变量,就需要用 export 关键字输出该变量,再在其他文件中通过 import 加载。

通常情况下,export 输出的名字就是本来的名字,import 时也需要引入一样的名字。下面举例几种 export 写法

/******* 直接输出变量、函数或者类 *******/
// export
export var firstName = 'Michael';
export function multiply(x, y) {
  return x * y;
};
// import
import { firstName, multiply } from '...';


/******* 间接输出变量 *******/
// export
var firstName = 'Michael';
export { firstName };	// 单独输出需要加大括号
// import
import { firstName } from '...';


/******* 输出变量重命名 *******/
// export
function v1() { ... };
class v2() { ... };
export { v1 as streamV1, v2};
// import
import { streamV1, v2 as streamV2} from '...';
            
            
/******* 模块整体加载 *******/
// export
function v1() { ... };
class v2() { ... };
export { v1, v2};
// import
import * as test from '...';
test.v1(..);
new test.v2();
            
            
/******* 默认输出加载 *******/      
// export
function jquery() {
  func1(){}
};
export default jquery;
// import
import jquery from '...';
jquery.fun1(..);

上面列出了常用的几种,但是还有输入输出复写、模块继承等等高级用法,可以参考文档。

Class

ES6 的 Class 其实就是一个javascript 创建类的语法糖。

所有 class 的功能都会 complie 成 prototype 形式

// ES6 Class
class Cat {
  consrtuctor(name, color) {
    this.name = name;
    this.color = color;
  }
  
  speak () {
      alert('Meeooow');
  }
}

var cat = new Cat('Tom', "black");


// Native Javascript Alternative
var Cat = function (name, color) {
    this.name = name;
  	this.color = color;
}

Cat.prototype.speak = function () {
    alter('Meeooow');
}

var cat = new Cat('Tom', 'black');

Good Example for Animation

export default class ScrollAnimation {

  /**
   * Headers because there will be a mobile and
   * a desktop header, both need to be transformed when scrolled
   * @param  {NodeList|Array<HTMLElement>} header
   * @param  {NodeList|Array<HTMLElement>} whiteBackground for animation purpose only
   */
  constructor(header, whiteBackground, headerBars) {
    this.previousScroll = 0;
    this.hoverActiveState(header);
    this.requestAnimationFrame(() => this.loop(header, whiteBackground, headerBars));
  }
  
  hoverActiveState (header) {...}
  static requestAnimationFrame () {...}
}
// other files
import ScrollAnimation from '../../../ScrollAnimation.js';
// this will automatically run constructor
const scroll = new ScrollAnimation(header, whiteBackground, headerBars)
// this is how to call static method without creating instance
ScrollAnimation.requestAnimationFrame();

Static Method

ES6 提供了静态方法,使得方法运行效率更高,也能在不实例化类的情况下使用静态方法,常用于创建一些 utility 方法,可以到处用。

创建时只需要在方法名前加上static即可 static function staticMethod(){...}

使用时分两种情况

/******* Case 1 构造函数、类内部使用 *******/
exampleClass.staticMethod(); // or
this.constructor.staticMethod();  

/******* Case 2 类外部使用 *******/
import exampleClass from '...';
exampleClass.staticMethod();

解构赋值

var [a, b, c] = [1, 2, 3];
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [,,third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

  • 字符串解构赋值

    const [a, b, c, d, e] = 'hello';
    a // "h"
    b // "e"
    c // "l"
    d // "l"
    e // "o"
    

用途

  • 交换变量的值

    [x, y] = [y, x];
    
  • 从函数返回多个值

    function example() {
      return [1, 2, 3];
    }
    var [a, b, c] = example();
    
  • 函数参数的定义

    // 参数是一组有次序的值
    function f([x, y, z]) { ... }
    f([1, 2, 3])
    
    // 参数是一组无次序的值
    function f({x, y, z}) { ... }
    f({x:1, y:2, z:3})
    

多行字符串

由于使用 ‘\n‘ 比较费事,ES6 增加了多行字符串,用 例如: `...` 来表示

alert(`多行
字符串
测试`)

模版字符串

以前凭借字符串都是用加号,如 var test = "这是一个"+ test + "实验";

ES6新增一种模版字符串 {$variable}

var name = "li";
var age = 20
alert('${name} is ${age} years old');

箭头函数

([param] [, param]) => {
	//statements
}
// 等同于
function ([param] [, param]) {
  //statements
}
// Example
// 一个空箭头函数,返回undefined
let empty = () => {};

// 返回"foobar"
(() => "foobar")()

var simple = a => a > 15 ? 15 : a;
simple(16); // 15
simple(10); // 10

// 多个参数
var complex = (a, b) => {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

this 作用域提升

在普通的 function(){} 函数中,如果在函数体内部使用this,则该this 拥有自己的作用域, 所以我们常常使用var that = this 的方式用在函数体内用that 替换this

然而,箭头函数引用了父级的变量,构成了一个闭包。因此,在箭头函数的内部的this 指向父级作用域下的this,这样就再也没有必要去使用that = this

Map & Set

Iterable

  • for… of …

  • forEach

    var a = ['A', 'B', 'C'];
    a.forEach(function (element, index, array) {
        // element: 指向当前元素的值
        // index: 指向当前索引
        // array: 指向Array对象本身
        alert(element);
    });
    


JS in Browser

Node vs Element?

Node 是一切 DOM 层级中各类 object 的一个统称,包括了document node, HTML element node, text node, comment node 等等。所以笼统的说,DOM 里的任何对象都可以叫做 Node。

Elememt 只是许多node 类型中的一种,也是我们最常使用的那种DOM 元素。

Document.querySelector()?

我们常常使用的 documemt.getElementById() 以及 document.querySelector() 返回的其实都是一个node,一个特定类型的,只能是element 类型的node,所以有时候会分不清楚 node 与 element 的区别。

Document.querySelectorAll()?

同样, documemt.getElementByClass() 以及 document.querySelectorAll() 返回的其实是一个 nodeList,保存了多个 Nodes,但是具体来看,还是一个一个的Element。

nextSibling vs nextElementSibling

最好的例子就是 DOM.nextSibling()Dom.nextElementSibling()

语义上就可以理解的很清楚,nextSibling 返回Node,nextElementSibling 返回 Element。有些时候二者返回的内容是相同的,例如

<div class='item1'>item 1</div>
<div class='item2'>item 2</div>

<script>
document.querySelector('.item1').nextSibling; // div item2
document.querySelector('.item1').nextElementSibling; // div item2
</script>

但是有些时候 nextSibling 会返回一个不是 Element 类型的 Node 元素,如下

<div class='item1'>item 1</div>
I'm some text
<div class='item2'>item 2</div>

<script>
document.querySelector('.item1').nextSibling; // node.text
document.querySelector('.item1').nextElementSibling; // div item2
</script>

因为text 也是node 类型的一种,所以nextSibling 会返回一个text 类型的 node 对象。